﻿#include <stdio.h>
#include <string.h>
#include <inttypes.h>

#ifdef WIN32
#include <sys/timeb.h>
#include <time.h>
#else
#include <sys/time.h>
#include <sys/types.h>
#endif

#include <atomic>
#include <mutex>
#include "SLT_uuid.h"

#define UUID_LEN        36

/* microsecond per second. 1s=1000000us=1000000000ns*/
#define NSec100_Per_Sec        10000000
#define USec_Per_Sec        1000000
#define USec_Per_MSec        1000
#define NSec_Since_1582        ((uint64_t)(0x01B21DD213814000))

typedef unsigned short word_t;
typedef unsigned int dword_t;

/*========================================================================================
                            Private Functions
========================================================================================*/
static bool isbigendian()
{
    int c = 1;
    return ( *((unsigned char *) &c) == 1 )? false: true;
};

static void swap_word( int size_bytes, void * ptr_word )
{
    int        i;
    unsigned char       temp;
    for( i=0; i < size_bytes/2; i++ )
    {
        temp = ((unsigned char *) ptr_word)[i];
        ((unsigned char *) ptr_word)[i] = ((unsigned char *) ptr_word)[size_bytes-i-1];
        ((unsigned char *) ptr_word)[size_bytes-i-1] = temp;
    }
};

static void write_word( unsigned char* stream, word_t val )
{
    memcpy(stream, &val, 2);
    if( isbigendian() ) swap_word( 2, stream );
};

static void write_dword( unsigned char* stream, dword_t val )
{
    memcpy(stream, &val, 4);
    if( isbigendian() ) swap_word( 4, stream );
};

static void  read_word( const unsigned char* stream, word_t* val )
{
    memcpy( val, stream, 2 );
    if( isbigendian() )    swap_word( 2, val );
};

static void  read_dword( const unsigned char* stream, dword_t* val )
{
    memcpy( val, stream, 4 );
    if( isbigendian() )    swap_word( 4, val );
};

static bool is_xdigit(char c)
{
    /* isxdigit returns a non-zero value if c is a hexadecimal digit (A – F, a – f, or 0 – 9). */
    return ((c>='A'&&c<='F')||(c>='a'&&c<='f')||(c>='0'&&c<='9'))? true : false;
};


/* make a pseudorandom numbel based on current time*/
static int pseudo_rand()
{
#ifdef _USE_32BIT_TIME_T
    assert(0);
#endif
    
#ifdef WIN32
    struct _timeb  timebuf;
#pragma warning(push)    /* C4996 */
#pragma warning( disable : 4996 )
    _ftime64(&timebuf);
#pragma warning(pop)    /* C4996 */
    srand((uint32_t) ((((uint32_t)timebuf.time&0xFFFF)+(uint32_t)timebuf.millitm)^(uint32_t)timebuf.millitm));
#else
	struct timeval t;
    gettimeofday(&t, NULL);
    srand((uint32_t) ((((uint32_t)t.tv_sec&0xFFFF)+(uint32_t)t.tv_usec)^(uint32_t)t.tv_usec));
#endif
    return rand();
};


/*========================================================================================
                            Public Functions
========================================================================================*/

/**
 * internal
 * ingroup uuid
 * The thread synchronization lock used to guarantee UUID uniqueness
 * for all the threads running within a process.
 */
SLT_API void slt_uuid_create(slt_uuid_t* u)
{    
    static std::mutex  s_mutex;
    static bool        initialized = false;
    static int64_t    timestamp;
    static uint32_t    advance;
    static uint16_t    clockseq;
    static uint16_t    node_high;
    static uint32_t    node_low;
    int64_t            time;    /* unit of 100ns */
    uint16_t            nowseq;
    int                r;

    #ifdef _USE_32BIT_TIME_T
        assert(0);
    #endif
	assert(u);


#ifdef WIN32
    struct _timeb  tv;
#pragma warning(push)    /* C4996 */
#pragma warning( disable : 4996 )
    _ftime64(&tv);
#pragma warning(pop)    /* C4996 */

    /* time is counter of 100ns time interval since Oct.15, 1582 (NOT 1852) */
    time = ((uint64_t) tv.time) * USec_Per_Sec + ((uint64_t) tv.millitm*USec_Per_MSec);
#else
	struct timeval t;
    gettimeofday(&t, NULL);
	time = ((uint64_t) t.tv_sec) * USec_Per_Sec + ((uint64_t) t.tv_usec*USec_Per_MSec);
    //time = ((uint64_t)t.tv_sec) * USec_Per_Sec + ((uint64_t)t.tv_usec);
#endif	
    time = time * 10 + NSec_Since_1582;

    s_mutex.lock();     //加锁
    if (!initialized) 
    {
        timestamp = time;
        advance = 0;

        r = pseudo_rand();

        clockseq = r >> 16;
        node_high = r | 0x0100;
        
        node_low = pseudo_rand();
        
        initialized = true;
    } 
    else if (time < timestamp) 
    {
        timestamp = time;
        advance = 0;
        clockseq++;
    } 
    else if (time == timestamp) 
    {
        advance++;
        time += advance;
    } 
    else 
    {
        timestamp = time;
        advance = 0;
    }
    s_mutex.unlock();  //解锁

    nowseq = clockseq;

    assert(u);
    u->data1 = (dword_t) time;
    u->data2 = (word_t) ((time >> 32) & 0xffff);
    u->data3 = (word_t) (((time >> 48) & 0x0ffff) | 0x1000);
    write_word(&(u->data4[6]), (word_t) ((nowseq & 0x3fff) | 0x8000));    
    write_word(&(u->data4[4]), (word_t) (node_high));                    
    write_dword(&(u->data4[0]), (dword_t) (node_low));            
}

SLT_API std::string slt_uuid_to_string(const slt_uuid_t* uuid)
{
    char buf[37];
    slt_uuid_to_string(uuid, buf, sizeof(buf));
    return std::string(buf);
}
SLT_API void slt_uuid_to_string(const slt_uuid_t* u, char* oBuf, int inBufLen)
{
    //static char uuid_str[UUID_LEN+1];
    uint16_t a,b;
    uint32_t  c;
    read_word(&(u->data4[6]), &a);
    read_word(&(u->data4[4]), &b);
    read_dword(&(u->data4[0]), &c);

    snprintf(oBuf, inBufLen, "%08x-%04x-%04x-%04x-%04x%08x",
                u->data1,
                u->data2,
                u->data3,
                a, b, c);
}

/**
 * Compare two UUID's lexically
 *    return
 *      -1   u1 is lexically before u2
 *     0   u1 is equal to u2
 *     1   u1 is lexically after u2
*/
SLT_API int slt_uuid_compare(const slt_uuid_t *u1, const slt_uuid_t *u2)
{
    int i;

#define CHECK_COMP(f1, f2)  if ((f1) != (f2)) return ((f1) < (f2) ? -1 : 1);
    
    CHECK_COMP(u1->data1, u2->data1);
    CHECK_COMP(u1->data2, u2->data2);
    CHECK_COMP(u1->data3, u2->data3);

    for(i=0; i<8; i++)
        CHECK_COMP(u1->data4[i], u1->data4[i]);

#undef CHECK_COMP

    return 0;
}
